
import com.adobe.serialization.json.JSON;

import flash.display.StageDisplayState;
import flash.events.MouseEvent;
import flash.events.TimerEvent;
import flash.utils.Timer;

import mx.collections.ArrayCollection;
import mx.containers.Canvas;
import mx.controls.Button;
import mx.controls.Image;
import mx.controls.Label;
import mx.controls.Text;
import mx.core.SoundAsset;

//constants for payment determination
public var ymax:Number = 10;
public var k:Number;

//hit parameters
public var href:String;
public var hitId:String;
public var workerId:String;
public var assignmentId:String;
public var browser:String;

//data for all groups of clicks
public var distance:Number;
public var buttonHeight:Number = 420;
public var buttonWidth:Number = 25;
public var groupsOfData:ArrayCollection = new ArrayCollection();
public var priceForGroup:Number=0;
public var totalClicks:Number = 0;
public var clicksPerSet:int = 10;
public var groupCap:int = 100;
public var errorRateCap:Number =40; //max 100

//data for this group only (state that will get thrown away after the group is over)
public var clickDataArray:ArrayCollection = new ArrayCollection();
public var latestTime:Number;
public var clicks:int;
public var firstClickHappened:Boolean = false;
public var previousErrorRate:Number;

//initialize UI components
public var button1:Canvas = new Canvas();
public var button2:Canvas = new Canvas();
public var repeatInstructions:Text = new Text();
public var submitText:Label = new Label();
public var submitButton:Button = new Button();
public var submitButton1:Button = new Button();
public var submitText1:Label = new Label();
public var keepPlayingButton:Button = new Button();
public var paymentText:Text = new Text();
public var timeDiffText:Text = new Text();
public var errorRateText:Text = new Text();

[Embed(source="images/fittslawillustration.jpg")]
public var experimentIllustration:Class; 

public var timer:Timer;
/*
[Embed(source="sounds/windowsding.mp3")]
[Bindable]
public var BeepSound:Class;
public var beep:SoundAsset = new BeepSound() as SoundAsset;

[Embed(source="sounds/beep.mp3")]
[Bindable]
public var DingSound:Class;
public var ding:SoundAsset = new DingSound() as SoundAsset;
public var channel:SoundChannel;
*/


public function init():void{
	/*gup('assignmentId') == "ASSIGNMENT_ID_NOT_AVAILABLE"*/
   if (gup('assignmentId') == "ASSIGNMENT_ID_NOT_AVAILABLE"){        
    //{ - for testing the look of the updated instructions 
    //  if (true==true) { 
        var instructions:Text = new Text();
        instructions.htmlText = "<b>Task</b> " + 
        		"When you accept this HIT, you will see two rectangles on the screen. <br>" + 
        		"You need to click back and forth between the rectangles as <b>quickly</b> and <b>accurately</b> as you can.<br>" + 
" Always start by clicking on the left rectangle. Your current target will always be colored green.<br>  " + 
"If you make a mistake and click outside the green rectangle, just keep going.<br><br>"+
"<b>Payment</b> You will be paid at least 10 cents for accepting this HIT and doing the first group of 10 clicks.<br>" + 
"After the first group of 10, you will have the chance to continue and earn more money.<br> " + 
"Note that you can earn at <b>most</b> 10 additional cents in total from doing more groups, and that <br>" + 
"each group is worth less than the previous group. If you earn a fractional amount of cents, <br>" + 
"we will treat the fraction as your probability of getting another cent - i.e., if you <br>"+
"you earn 7.5 cents, you'll be paid 7 cents with probability 1/2 and 8 cents with probability 1/2.<br><br>"+
"<b>End</b> Between groups of 10, you will have a chance to rest for a short time (at most 5 minutes).<br>" + 
"You can stop after any group of 10 by submitting the HIT <br> and you will be paid the base rate plus all additional earnings.<br><br>"+
"Accept the HIT to begin.<br><br>"
    	canvas.addChild(instructions)
    	
    	var illustration:Image = new Image()
    	illustration.x = 0;
    	illustration.y = 300;
    	illustration.source=experimentIllustration;
    	canvas.addChild(illustration)
    	
    }else{  
		//determine the distance between the two buttons (50% chance 300px, 50% chance 600px)
		var rand:Number = Math.random();
		if(rand < 0.5){
			distance = 100;
		}else{
			distance = 600;
		}
        k = Math.log(.5)/10;  
		// randomly assigns a half-life to the worker 
		//var rand2:Number = Math.random();
		//if(rand2 < 0.5){
		//	k = Math.log(.5)/5;
		//}else{
		//	k = Math.log(.5)/10; 
		//}
   
		//set the price for the first group (after every group this will get updated)
		priceForGroup = calculateCurrentGroupPrice();
		
		//repeating the instructions
		repeatInstructions.htmlText = "Click back and forth between the rectangles as <b>quickly</b> and <b>accurately</b> as you can.<br><br>Your current target will always be colored green.";
		repeatInstructions.x = 70;
		repeatInstructions.y = 30;
		canvas.addChild(repeatInstructions);
	
		//make the buttons	
		button1.height = buttonHeight;
		button1.id = "button1"
		button1.width = buttonWidth;
		button1.x = 50;
		button1.y = 100;
		button1.styleName = "activeButton"
		canvas.addChild(button1)
		
		button1.addEventListener(MouseEvent.CLICK, function(e:MouseEvent):void{
			if(firstClickHappened == false){
				firstClickHappened = true;
				//trace('first click')
				var d:Date = new Date();
				latestTime = d.time
				handleSuccessfulClick(button1, e, true)	
			}else{
				if(button1.styleName == "activeButton"){
					handleSuccessfulClick(button1, e)
				}else{
					handleErrorClick(e)
				}
			}
		})	
		
		button2.id = "button2"
		button2.height = buttonHeight;
		button2.width = buttonWidth;
		button2.x = button1.x +	distance;
		button2.y = 100
		canvas.addChild(button2)
		button2.styleName = "inactiveButton"		
		
		button2.addEventListener(MouseEvent.CLICK, function(e:MouseEvent):void{	
			if(firstClickHappened){	
				if(button2.styleName == "activeButton"){
					handleSuccessfulClick(button2, e)
				}else{
					handleErrorClick(e)
				}
			}
		})
		canvas.addEventListener(MouseEvent.CLICK, function(e:MouseEvent):void{
			if(firstClickHappened){
				handleErrorClick(e);
			}
		})
		
		//Make the results screen
		keepPlayingButton.x = 150;
		keepPlayingButton.y = 50;
		canvasResults.addChild(keepPlayingButton);	
		keepPlayingButton.addEventListener(MouseEvent.CLICK, function():void{
			canvas.visible = true;
			canvasResults.visible = false;
		})		
		
		submitButton.label="Stop and Submit the HIT"
		submitButton.x = 500;
		submitButton.y = 50;
		canvasResults.addChild(submitButton);	
		submitButton.addEventListener(MouseEvent.CLICK, function():void{
			canvas.visible = false;
			canvasResults.visible = false;
			demographics.visible = true;
		})
		
		canvasResults.addChild(timeDiffText);
		timeDiffText.x = 200;
		timeDiffText.y = 150;		

		canvasResults.addChild(errorRateText);
		errorRateText.x = 200;
		errorRateText.y = 200;
				
		canvasResults.addChild(paymentText);
		paymentText.x = 200;
		paymentText.y = 250;
				
	

		//canvasErorr
		submitButton1.label="Submit"
		submitButton1.x = 500;
		submitButton1.y = 50;
		canvasTooManyErrors.addChild(submitButton1);	
		submitButton1.addEventListener(MouseEvent.CLICK, function():void{
			submitHIT();
		})
		
		submitText1.text = "Thanks for playing, please submit the HIT."
		submitText1.x = 200;
		submitText1.y = 50;
		canvasTooManyErrors.addChild(submitText1);
		
		
		//demographics
		//the submit button should be inactive until everything is written it.
		//submitWithDemographics.enabled = {reportedEnjoyment ! = null} //{enjoyment.selection ! = null && gender.selection ! = null };
		submitWithDemographics.addEventListener(MouseEvent.CLICK, function():void{
			submitHIT();
		})
		
		href = ExternalInterface.call("document.location.toString");
		hitId = getURLParam("hitId", href);
		workerId = getURLParam("workerId", href);
		assignmentId = getURLParam("assignmentId", href);
		browser = ExternalInterface.call("navigator.userAgent.toString");	
		var params:Object = { 	hitId: hitId,
								workerId: workerId,
								assignmentId: assignmentId,
								distance: distance,
								browser: browser
								 };			
		logInit.send(params);
    }
}

function handleSuccessfulClick(button:Canvas, e:MouseEvent, firstClick:Boolean=false):void{
	//trace('successful click')
	//update latestTime
	var d:Date = new Date();
	var time:Number = d.time;
	var timeDiff:Number = 0	
	
	//check that this isn't the first time the button has been clicked.
	if(!firstClick){
		//nullify timer
		timer.stop();
		//add current time difference to array	
		timeDiff=time-latestTime 
	}
	var thisTimeDiff:* = {};	
	thisTimeDiff.targetId = button.id
	thisTimeDiff.clickNum = clicks;
	thisTimeDiff.hitOrMiss = "hit";
	var d1:Date = new Date();
	thisTimeDiff.time = d1.time
	thisTimeDiff.timeDiff = timeDiff
	thisTimeDiff.x =  e.localX - button1.width/2
	thisTimeDiff.y =  button1.height/2 - e.localY
	//trace("x: "+thisTimeDiff.x+" y: "+thisTimeDiff.y);
	clickDataArray.addItem(thisTimeDiff);
		
	latestTime = time
	
	//startCountdown
    timer = new Timer(4000, 1)
    timer.addEventListener("timerComplete", function(event:TimerEvent):void{
    	//Alert.show('Sorry, you took more than 4 seconds between clicks.  The HIT will be submitted and you will get paid for the clicks you have made.  Thank you for playing!');
    	//submitHIT();
    });
    timer.start();
    
	//switch the enables button
	
	if(button.id=="button1"){
		button1.styleName= "inactiveButton"
		button2.styleName = "activeButton" 
	}
	if(button.id=="button2"){
		button1.styleName = "activeButton" 
		button2.styleName= "inactiveButton"
	}		
	
	//update the clickText
	clicks++;
	
	checkToEndGroup()

	//make sure the canvas below doesn't get the click event. (so it won't register as an error)
	e.stopImmediatePropagation();	
}

public function checkToEndGroup():void{
	if(clicks==clicksPerSet){	
		//add these clicks to the totalClicks
		totalClicks = totalClicks+clicksPerSet;		
		var totalEarnings:Number = totalEarnings();
		
		//check that the cap is not exceeded
		if(groupsOfData.length + 1 >=groupCap){
			canvasResults.removeChild(keepPlayingButton);
			submitButton.label = "Submit";
			submitButton.x = 250;
		}else{
			keepPlayingButton.label="Do Another Group of "+clicksPerSet+" Clicks for "+calculatePayment(groupsOfData.length+2)+" cents"
		}
		
		//Make the statistics (but we don't have to display them all.
		//1. total earnings.
		if(totalEarnings ==1){
			paymentText.htmlText = "You have earned <b>"+ totalEarnings+" cent </b> in bonuses so far";
		}else{
			paymentText.htmlText = "You have earned <b>"+ totalEarnings+" cents </b> in bonuses so far";
		}
		
		//2. average click time
		var hits:int = 0
		var totalClickTimeDiff:Number = 0
		for(var k:int=0; k<clickDataArray.length; k++){
			var thisOb:* = clickDataArray.getItemAt(k);
			if(thisOb.hitOrMiss =="hit"){
				hits++;
			}
			totalClickTimeDiff = totalClickTimeDiff+thisOb.timeDiff
		}
		var avgTimeDiff:Number = totalClickTimeDiff/9000
		avgTimeDiff *= 100
		avgTimeDiff = Math.round(avgTimeDiff)
		avgTimeDiff = avgTimeDiff/100
		errorRateText.htmlText = "Your average click time is <b>"+avgTimeDiff+"</b> seconds"
		
		//3. error rate
		var errRate:Number = hits/clicksPerSet
		errRate *=100		
		timeDiffText.htmlText = "Your clicking accuracy is <b>"+errRate +"%</b>";

		
		//check the error rate
		var errRateEnd:Boolean = false;
		if(groupsOfData.length>0){
			//check if this error rate and the previous one are below the threshhold
			if(previousErrorRate< errorRateCap && errRate<errorRateCap){
				errRateEnd = true;
			}			
		}		
		previousErrorRate = errRate;	
		
		//add clickDataArray to groupData
		var groupOfData:* = {};
		groupOfData.avgTimeDiff = avgTimeDiff;
		groupOfData.errRate = errRate;
		groupOfData.priceForGroup = priceForGroup;				
		groupOfData.clickData = clickDataArray.source
		
		//groupsOfData.addItem(clickDataArray.source)
		groupsOfData.addItem(groupOfData)
		
		//update the db to have this click set
		var clickData:String = JSON.encode(groupsOfData.source)
		var params:Object = {
						workerId: workerId,
						assignmentId: assignmentId,
						clickData: clickData
						 };	
		logUpdateClickData.send(params);
		
		//reset some state variables for the next group of clicks
		clickDataArray = new ArrayCollection();
		latestTime = null;
		firstClickHappened = false			
		clicks=0;
		//notice this is AFTER groupsOfData has been updated, so technically is the the NEXT group of hits' price.
		priceForGroup = calculateCurrentGroupPrice();

		//switch to the results display screen
		if(errRateEnd){
			canvasTooManyErrors.visible = true;
			canvas.visible = false;
			canvasResults.visible = false;			
		}else{
			canvas.visible = false;
			canvasResults.visible = true;
		}		
	}	
}

public function handleErrorClick(e:MouseEvent):void{
	trace('error click')		
	
	var thisError:* = {};	
	var d:Date = new Date();
	thisError.time = d.time
	thisError.clickNum = clicks;
	thisError.hitOrMiss = "miss"
	
	var time:Number = d.time;	
	var timeDiff:Number =time-latestTime 	
	thisError.timeDiff = timeDiff;
	//update the latest time
	latestTime = time
	
	if(button1.styleName == "activeButton"){	
		var timer:Timer = new Timer(100,1);
		timer.addEventListener(TimerEvent.TIMER_COMPLETE, function():void{
			//trace('stop');
			button1.styleName= "inactiveButton"
		}) 						
		button1.styleName= "errorButton"
		timer.start()

		button2.styleName = "activeButton" 
		
		thisError.x = e.stageX - button1.x +button1.width/2
		thisError.y = button1.y +button1.height/2 - e.stageY
		thisError.targetId = "button1"
	}else{
		button1.styleName = "activeButton" 
		var timer:Timer = new Timer(100,1);
		timer.addEventListener(TimerEvent.TIMER_COMPLETE, function():void{
			//trace('stop');
			button2.styleName= "inactiveButton"
		}) 						
		button2.styleName= "errorButton"
		timer.start()
		
		thisError.x = e.stageX - button2.x +button2.width/2  
		thisError.y = button2.y +button2.height/2 -	e.stageY	
		thisError.targetId = "button2"
	}
	e.stopImmediatePropagation();
	
	//trace("x: "+thisError.x+" y: "+thisError.y);
	clickDataArray.addItem(thisError);
	
	clicks++;
	//clickText.text = clicks+" clicks";	
	
	checkToEndGroup()
	
	//feedback		
	//channel = beep.play();
	
}

public function submitHIT():void{
	Application.application.stage.displayState = StageDisplayState.NORMAL;

	
	var clickData:String = JSON.encode(groupsOfData.source)
	var payment:Number = 0;
	for each(var r:* in groupsOfData){
		payment += r.priceForGroup
	} 
	
	var params:Object = { 	
							workerId: workerId,
							assignmentId: assignmentId,	
							clickData: clickData,
							totalClicks: totalClicks,
							enjoyment: enjoyment.selectedValue,
							age: ageReport.text,
							gender: gender.selectedValue,
							inputDevice: inputDevice.selectedValue,
							otherInputDevice: otherInput.text,
							payment: payment
							 };
	logSubmit.send(params)
	
	//submit the HIT
	var sandbox = "false";
	if(href.indexOf("workersandbox")!=-1){		
		sandbox = "true"				
	}	
	if (ExternalInterface.available){
		ExternalInterface.call("submitForm",sandbox, href);  
	}
		
}

//modified from http://mattwhite.me/11tmr.nsf/D6Plinks/MWHE-695L9Z
public function getURLParam(strParamName:String, strHref:String):String{
  var strReturn:String = "";
  if ( strHref.indexOf("?") > -1 ){
    var strQueryString:String = strHref.substr(strHref.indexOf("?")).toLowerCase();
    var aQueryString:Array = strQueryString.split("&");
    for ( var iParam:int = 0; iParam < aQueryString.length; iParam++ ){
      if (
aQueryString[iParam].indexOf(strParamName.toLowerCase() + "=") > -1 ){
        var aParam:Array = aQueryString[iParam].split("=");
        strReturn = aParam[1];
        break;
      }
    }
  }
  return strReturn;
} 

//these methods should only be used on the last screen
//they are sensitive to when you call them because the length of groupsOfData changes at a particular time.
public function calculateCurrentGroupPrice():Number{
	//Add 1 because the length of groups is the number of groups you've ALREADY done. The current price is for the next one.
	var n:int = groupsOfData.length +1; 
	trace('n: '+n+' calculateCurrentGroupPrice: '+calculatePayment(n));
	return calculatePayment(n);
}


public function calculatePayment(n:int):Number{
	var ret = ymax * (Math.exp(k*(n-1)) - Math.exp(k*n));
	ret *= 100;
	ret = Math.round(ret)	 
	ret = ret/100
	return ret;	
}

public function totalEarnings():Number{
	var currentTotal:Number = priceForGroup;
	for each(var r:* in groupsOfData){
		currentTotal += r.priceForGroup
	} 
	return currentTotal;
}
